﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Threading;
using System.IO;
using System.Net.NetworkInformation;

namespace Chat
{
    public class Server
    {
        protected int _port = 4520;
        protected Socket _chatListenSocket;

        public event EventHandler<MessageEventArgs> UserJoined = delegate { };
        public event EventHandler<MessageEventArgs> UserLeft = delegate { };
        public event EventHandler<MessageEventArgs> MessageReceived = delegate { };
        public event EventHandler<ServerEventArgs> ServerEvent = delegate { };

        protected Thread _chatConnectionThread = null;
        protected Thread _chatPollingThread = null;

        protected List<UserSocket> _userSockets = new List<UserSocket>();

        protected DateTime _lastKeepAlive = DateTime.Now;

        protected bool _serverRunning = false;

        public Server(int port)
        {
            _port = port;
        }

        protected void LogServerEvent(string eventText)
        {
            ServerEvent(this, new ServerEventArgs(DateTime.Now, eventText));
        }

        public void Start()
        {
            if (!_serverRunning)
            {
                IPEndPoint chatServerEndPoint = new IPEndPoint(IPAddress.Any, _port);

                _chatListenSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                _chatListenSocket.Bind(chatServerEndPoint);
                _chatListenSocket.Listen(256);

                LogServerEvent("Chat Server listening on port " + _port.ToString());
                
                _serverRunning = true;

                _chatConnectionThread = new Thread(connectionHandler);
                _chatConnectionThread.Start();

                _chatPollingThread = new System.Threading.Thread(pollingHandler);
                _chatPollingThread.Start();
                

                LogServerEvent("Server Started");
            }
        }

        public void Stop()
        {
            UserSocket[] userSockets = this._userSockets.ToArray();
            for (int n = 0; n < userSockets.Length; n++)
            {
                UserSocket userSocket = userSockets[n];
                removeUser(userSocket);
            }

            _serverRunning = false;
            _chatListenSocket.Close();
        }

        protected void SendUserListToUser(UserSocket userSocket)
        {
            var userNames = from u in _userSockets
                             where u.Name != null && u.Name != String.Empty
                             select u.Name;

            Message userListMessage = new Message(DateTime.Now, MessageType.UserList, userNames.ToArray());
            userSocket.Send(userListMessage);
        }

        protected void SendMessageToAllNamedUsers(Message message)
        {
            var namedUsers = (from u in _userSockets
                              where u.Name != null && u.Name != String.Empty
                              select u).ToArray();
            
            SendMessageToUsers(message, namedUsers);
        }

        protected void SendMessageToAllUsers(Message message)
        {
            SendMessageToUsers(message, _userSockets.ToArray());
        }

        protected void SendMessageToUsers(Message message, UserSocket[] userSockets)
        {
            foreach (UserSocket userSocket in userSockets)
                userSocket.Send(message);
        }

        private void connectionHandler()
        {
            while (_serverRunning)
            {
                waitForConnections();
            }
        }

        private void pollingHandler()
        {
            while (_serverRunning)
            {
                checkMessages();

                if (_lastKeepAlive < DateTime.Now.AddSeconds(-5))
                {
                    if (_userSockets.Count > 0)
                    {
                        LogServerEvent("Sending Keep Alive To Connected Sockets...");

                        Message keepAliveMessage = new Message(DateTime.Now, MessageType.KeepAlive, new string[] { });
                        SendMessageToAllUsers(keepAliveMessage);
                    }

                    _lastKeepAlive = DateTime.Now;
                }

                System.Threading.Thread.Sleep(5);
            }
        }

        private void waitForConnections()
        {
            try
            {
                Socket newSocket = _chatListenSocket.Accept();
                LogServerEvent("New Chat Connection Received");
                addUser(newSocket);
            }
            catch (SocketException)
            {
                // close called or some failure....
            }
        }

        private void checkMessages()
        {
            UserSocket[] currentSockets = _userSockets.ToArray();

            foreach (UserSocket socket in currentSockets)
            {
                if (socket.DataAvailable)
                    socket.ReceiveMessages();
            }
        }

        private void addUser(Socket newSocket)
        {
            lock (_userSockets)
            {
                UserSocket newUserSocket = new UserSocket(newSocket, ReceiveMode.Manual);
                newUserSocket.Error += new EventHandler<UserSocketEventArgs>(userSocket_Error);
                newUserSocket.NewMessage += new EventHandler<MessageEventArgs>(userSocket_NewMessage);

                _userSockets.Add(newUserSocket);
            }
        }

        private void removeUser(UserSocket userSocket)
        {
            lock (_userSockets)
            {
                if (_userSockets.Contains(userSocket))
                    _userSockets.Remove(userSocket);
            }

            if (!string.IsNullOrEmpty(userSocket.Name))
            {
                Message userLeft = new Message(DateTime.Now, MessageType.UserLeft, new string[] { userSocket.Name });
                SendMessageToAllNamedUsers(userLeft);
            }

            LogServerEvent("Closing Socket");
            userSocket.Close();
        }

        private void userSocket_NewMessage(object sender, MessageEventArgs e)
        {
            UserSocket userSocket = sender as UserSocket;

            switch (e.Message.Type)
            {
                case MessageType.Text:
                    if (userSocket.Name != String.Empty)
                    {
                        SendMessageToAllNamedUsers(e.Message);
                    }
                    else
                    {
                        LogServerEvent("Unauthenticated Message Received. Dropping Client");
                        removeUser(userSocket);
                    }
                    break;

                case MessageType.SetNameRequest:

                    string requestedName = e.Message.Data[0];

                    bool exists = (from currentUserSocket in this._userSockets
                                   where currentUserSocket.Name == requestedName
                                   select currentUserSocket).Any();

                    if (exists)
                    {
                        Message setNameDenied = new Message(DateTime.Now, MessageType.SetNameDenied, new string[] { "Name Already In Use." });
                        userSocket.Send(setNameDenied);
                        removeUser(userSocket);
                    }
                    else
                    {
                        userSocket.Name = requestedName;
                        
                        Message nameSet = new Message(DateTime.Now, MessageType.NameSet, new string[] { requestedName });
                        userSocket.Send(nameSet);

                        Message newUserMessage = new Message(DateTime.Now, MessageType.UserJoined, new string[] { requestedName });
                        SendMessageToAllNamedUsers(newUserMessage);
                    }

                    break;

                case MessageType.Disconnect:
                    LogServerEvent("Received Disconnect");
                    removeUser(userSocket);
                    break;

                case MessageType.UserListRequest:
                    SendUserListToUser(userSocket);
                    break;

                default:
                    LogServerEvent("Unhandled Message Type: " + e.Message.Type.ToString());
                    break;
            }
        }

        private void userSocket_Error(object sender, UserSocketEventArgs e)
        {
            UserSocket userSocket = sender as UserSocket;

            lock (_userSockets)
            {
                if (_userSockets.Contains(userSocket))
                    _userSockets.Remove(userSocket);
            }

            LogServerEvent("Error Occurred In User Socket: " + e.Message);
        }
    }
}
